/*
 * ProGuard -- shrinking, optimization, obfuscation, and preverification of Java bytecode.
 *
 * Copyright (c) 2002-2018 GuardSquare NV
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package proguard.classfile.util;

import proguard.classfile.*;
import proguard.classfile.attribute.*;
import proguard.classfile.attribute.visitor.*;
import proguard.classfile.editor.*;
import proguard.classfile.instruction.*;
import proguard.classfile.instruction.visitor.InstructionVisitor;
import proguard.classfile.visitor.ClassVisitor;
import proguard.evaluation.BasicInvocationUnit;
import proguard.evaluation.value.*;
import proguard.optimize.evaluation.PartialEvaluator;

/**
 * This ClassVisitor replaces array initialization instructions with optimized primitive array constants.
 *
 * These constants are not supported by any Java specification and therefore only for internal use.
 *
 * @see PrimitiveArrayConstantReplacer
 * @author Thomas Neidhart
 */
public class ArrayInitializationReplacer extends SimplifiedVisitor implements ClassVisitor,

    // Implementation interfaces.
    AttributeVisitor, InstructionVisitor {
    private final ValueFactory valueFactory = new ParticularValueFactory(new BasicValueFactory());
    private final PartialEvaluator partialEvaluator =
        new PartialEvaluator(valueFactory, new BasicInvocationUnit(valueFactory), true);
    private final ArrayInitializationMatcher arrayInitializationMatcher =
        new ArrayInitializationMatcher(partialEvaluator);
    private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor();

    private ConstantPoolEditor constantPoolEditor;
    private int lastInstructionOffset;
    private int lastInstructionStackPushCount;
    private int arrayInitializationStart;
    private int arrayInitializationEnd;

    // Implementations for ClassVisitor.

    public void visitProgramClass(ProgramClass programClass) {
        constantPoolEditor = new ConstantPoolEditor(programClass);

        // Visit all methods that have "NEWARRAY" instructions.
        programClass.methodsAccept(new AllAttributeVisitor(new ArrayInitializationFilter(this)));
    }

    // Implementations for AttributeVisitor.

    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}

    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) {
        codeAttributeEditor.reset(codeAttribute.u4codeLength);

        partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute);

        lastInstructionOffset = -1;
        lastInstructionStackPushCount = -1;
        arrayInitializationStart = -1;
        arrayInitializationEnd = -1;
        codeAttribute.instructionsAccept(clazz, method, this);

        if (codeAttributeEditor.isModified()) {
            codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute);
        }
    }

    // Implementations for InstructionVisitor.

    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset,
        Instruction instruction) {
        // Verify that the previous instruction pushed the array size on the
        // stack: the java compiler will always do so, but obfuscators may
        // have reordered the instructions.
        if (instruction.opcode == InstructionConstants.OP_NEWARRAY && lastInstructionStackPushCount == 1) {
            if (arrayInitializationMatcher.matchesArrayInitialization(clazz, method, codeAttribute, offset,
                (SimpleInstruction)instruction)) {
                Object values = arrayInitializationMatcher.array();
                int constantIndex = constantPoolEditor.addPrimitiveArrayConstant(values);

                // We need to replace the previous instruction, which pushes
                // the array length onto the stack.
                codeAttributeEditor.replaceInstruction(lastInstructionOffset,
                    new ConstantInstruction(InstructionConstants.OP_LDC, constantIndex));

                // Remove the newarray instruction itself.
                codeAttributeEditor.deleteInstruction(offset);

                // Mark the start/end of the array initialization sequence.
                arrayInitializationStart = arrayInitializationMatcher.arrayInitializationStart();
                arrayInitializationEnd = arrayInitializationMatcher.arrayInitializationEnd();
            }
        }

        // Remove any instruction inside the array initialization sequence.
        if (arrayInitializationEnd != -1 && offset >= arrayInitializationStart && offset <= arrayInitializationEnd) {
            codeAttributeEditor.deleteInstruction(offset);
        }

        lastInstructionOffset = offset;
        lastInstructionStackPushCount = instruction.stackPushCount(clazz);
    }

    /**
     * Private utility class to visit only CodeAttributes that contain "NEWARRAY" instructions.
     */
    private static class ArrayInitializationFilter extends SimplifiedVisitor implements AttributeVisitor {
        private final AttributeVisitor acceptedVisitor;

        public ArrayInitializationFilter(AttributeVisitor acceptedVisitor) {
            this.acceptedVisitor = acceptedVisitor;
        }

        // Implementations for AttributeVisitor.

        public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}

        public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) {
            boolean delegateVisit = false;
            // Directly iterate of all instructions and exit early if
            // we encounter a "NEWARRAY" instruction.
            for (int offset = 0; offset < codeAttribute.u4codeLength;) {
                Instruction instruction = InstructionFactory.create(codeAttribute.code, offset);
                if (instruction.opcode == InstructionConstants.OP_NEWARRAY) {
                    delegateVisit = true;
                    break;
                }

                offset += instruction.length(offset);
            }

            if (delegateVisit) {
                acceptedVisitor.visitCodeAttribute(clazz, method, codeAttribute);
            }
        }
    }
}
